package com.xiashitech.agent.instrumentation.suppress.runnable.inst;

import com.xiashitech.agent.cache.status.CacheStatus;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.instrumentation.utils.SuppressUtil;
import com.xiashitech.agent.utils.DurationPrinter;
import io.opentelemetry.javaagent.bootstrap.executors.PropagatedContext;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.shaded.instrumentation.api.util.VirtualField;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import java.lang.reflect.Method;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class XSRunnableInstrumentation implements TypeInstrumentation {

  @Override
  public ElementMatcher<TypeDescription> typeMatcher() {
    return implementsInterface(named(Runnable.class.getName()));
  }

  @Override
  public void transform(TypeTransformer transformer) {
    transformer.applyAdviceToMethod(
        named("run").and(takesArguments(0)).and(isPublic()),
            XSRunnableInstrumentation.class.getName() + "$RunnableAdvice");
  }

  @SuppressWarnings("unused")
  public static class RunnableAdvice {

    @Advice.OnMethodEnter(suppress = Throwable.class)
    public static void enter(@Advice.This Runnable thiz) {
      //DurationPrinter durationPrinter = new DurationPrinter("XSRunnableInstrumentation");
      if(CacheStatus.cannotRun()) {
        return;
      }
      VirtualField<Runnable, PropagatedContext> virtualField =
          VirtualField.find(Runnable.class, PropagatedContext.class);
      PropagatedContext propagatedContext = virtualField.get(thiz);
      if (propagatedContext != null && SuppressUtil.isSuppress(thiz.getClass().getName())) {
        try {
          Method clear = propagatedContext.getClass().getDeclaredMethod("clear");
          clear.setAccessible(true);
          clear.invoke(propagatedContext);
        } catch (Throwable e) {
          StartConfig.throwableStackPrinter(e);
        }
      }
      //durationPrinter.printElapseDuration("RunnableAdvice b");
    }
  }
}
